// Returns an xmlhttprequest object.
function AjaxThing() {
  var xmlhttprequest = null;
  if(window.XMLHttpRequest) {
    try {
      xmlhttprequest = new XMLHttpRequest();
    }
    catch (e) {
    }
  }
  else if (window.ActiveXObject) {
    try {
      xmlhttprequest = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e) {
      try {
        xmlhttprequest = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch (e) {
      }
    }
  }
  return xmlhttprequest;
}

/* 
 * The FeedUpdateBroker class implements a simple RSS fetcher and parser.
 * 
 */

// Constructor.
function FeedUpdateBroker() {
    this.httpReq = null;
    this.callback = null;
}

// Fetches a feed from the specified URL and calls the callback when the feed
// has been fetched and parsed, or if the process results in an error.
FeedUpdateBroker.prototype.fetchFeed = function(feedURL,
                                                callback) {
  // remember callback
  this.callback = callback;

  // create new XML HTTP request
  this.httpReq = new AjaxThing();

  // set callback
  var self = this;
  this.httpReq.overrideMimeType('text/xml');
  this.httpReq.onreadystatechange = function() { self.readyStateChanged(); };

  // append the current time after the URL to bypass caches
  var fullURL = feedURL;
  if (fullURL.indexOf("?") == -1) {
    fullURL += "?";
  }
  else {
    fullURL += "&";
  }
  fullURL += "nocache=" + (new Date().getTime());

  // initiate the request
  this.httpReq.open("GET", fullURL, true);
  this.httpReq.send(null);
};

// Callback for ready-state change events in the XML HTTP request.
FeedUpdateBroker.prototype.readyStateChanged = function() {
  // complete request?
  if(this.httpReq.readyState == 4) {
    // attempt to get response status
    var responseStatus = null;
    try {
      responseStatus = this.httpReq.status;
    }
    catch (noStatusException) {
      console.debug("FeedUpdateBroker.readyStateChanged: " +noStatusException);
    }

    // handle the response and call the registered callback
    this.callback.callback.call(this.callback.that,
                                this.handleResponse(this.httpReq,
                                                    responseStatus,
                                                    this.httpReq.responseXML));
  }
};

// Handles a completed response.
FeedUpdateBroker.prototype.handleResponse = function(response,
                                                     responseStatus,
                                                     xmlDoc) {
  console.debug("FeedUpdateBroker.handleResponse: " + response+ " " + xmlDoc);
  if (responseStatus == 200 && xmlDoc != null) {
    // Parse the
    var feedType = this.determineFeedType(xmlDoc);
    // node ref for iterating
    var node;

    // get last modified time - default to the zero
    var lastModified = null;

    // init feed items array
    var items = [];

    var descriptionTagName = 'description';
    var contentTagRegex = /content/;

    var feedTitle = this.determineFeedTitle(feedType, xmlDoc);
    var itemElements = xmlDoc.getElementsByTagName("item");
    var atomFeed = false;
    if(itemElements.length == 0) {
      itemElements = xmlDoc.getElementsByTagName("entry");
      descriptionTagName = 'summary';
      atomFeed = true;
    }
    /*var forTimes = 0;
    var whileTimes = 0;*/
    for(var i = 0; i < itemElements.length; i++) {
      /*forTimes++;
      alert("forTimes: " + forTimes + " whileTimes: " + whileTimes);*/
      // iterate through child nodes of this item and gather
      // all the data we need for a feed item
      var title = undefined;
      var date = undefined;
      var description = undefined;
      var url = undefined;
      var content = undefined;

      node = itemElements[i].firstChild;
      while(node != null) {
        /*whileTimes++;*/
        if(node.nodeType == Node.ELEMENT_NODE) {
          if(node.nodeName == "title") {
            // item title
            title = this.getTextOfNode(node);
          }
          else if(node.textContent != "" &&
                  (node.nodeName == "pubDate" ||
                   node.nodeName == "dc:date" ||
                   node.nodeName == "updated")) {
            // item publishing date
            date = this.getDateOfNode(node);
            if(date) {
              if(!lastModified) {
                lastModified = date;
              }
              else if(date > lastModified) {
                lastModified = date;
              }
            }
          }
          else if(node.nodeName == descriptionTagName) {
            // item description
            description = this.getTextOfNode(node);
          }
          else if(node.nodeName.search(contentTagRegex) != -1) {
            content = this.getTextOfNode(node);
          }
          else if(node.nodeName == "link") {
            // link URL
            if(atomFeed) {
              url = node.getAttribute('href');
            }
            else {
              url = this.getTextOfNode(node);
            }
          }
        }
        node = node.nextSibling;
      }
      // create the item and add to the items array
      items.push({ title: title,
                   date: date,
                   description: description,
                   content: content,
                   url: url });
    }
    // update was completed successfully
    return { URL: xmlDoc.URL,
             status: "ok",
             title: feedTitle,
             lastModified: lastModified,
             items: items };
  }
  else {
    // update failed
    return { URL: xmlDoc.URL,
             status: "error" ,
             statusText: response.statusText};
  }
};

FeedUpdateBroker.prototype.determineFeedType = function(xmlDoc) {
  var feedType = "";
  if(xmlDoc.getElementsByTagName("rss").length > 0) {
    feedType = "rss";
  }
  else if(xmlDoc.getElementsByTagName("feed").length > 0) {
    feedType = "atom";
  }
  return feedType;
};

FeedUpdateBroker.prototype.determineFeedTitle = function(feedType,
                                                         xmlDoc) {
  var feedTitle = "";
  if(feedType === "rss") {
    var titleElements = xmlDoc.getElementsByTagName("channel")[0]
                              .getElementsByTagName("title");
    feedTitle = titleElements[0].textContent;
  }
  if(feedType === "atom") {
    var titleElements = xmlDoc.getElementsByTagName("feed")[0]
                              .getElementsByTagName("title");
    feedTitle = titleElements[0].textContent;
  }
  return feedTitle;
};

// Returns the date as a Date object from a node representing a date
FeedUpdateBroker.prototype.getDateOfNode = function(node) {
  var dateString = this.getTextOfNode(node);
  var parsedDate = new Date();

  // split the string '2009-01-01T22:00:00Z' first into the date and time strings
  var timeStamp = Date.parse(dateString);
  /** On the S60 device the timeStamp is something like 1.3^-36 so
    * timeStampe > 1 covers that as well. */
  if(!isNaN(timeStamp) &&
     timeStamp > 1) {
    parsedDate.setTime(timeStamp);
  }
  else {
    parsedDate = this.parseDateFromString(dateString);
  }
  return parsedDate;
};

FeedUpdateBroker.prototype.parseDateFromString = function(string) {
    var parsedDate = new Date();
    var portions = string.split('T');
    // split the '2009-01-01' and '22:00:00Z' strings further by - and :, respectively
    var yymmdd = portions[0].split('-');
    var hhmmss = portions[1].split(':');
    hhmmss[2] = hhmmss[2].replace('Z', "");

    var year = parseInt(yymmdd[0]);
    // For some reason months are presented 0-11
    var month = parseInt(yymmdd[1]) -1;
    var day = parseInt(yymmdd[2]);
    var hour = parseInt(hhmmss[0]);
    var minute = parseInt(hhmmss[1]);
    var second = parseInt(hhmmss[2]);
    parsedDate.setFullYear(year, month, day);
    parsedDate.setHours(hour, minute, second, 0);
    return parsedDate;
};

// Returns the text of a node.
FeedUpdateBroker.prototype.getTextOfNode = function(node) {
  var buf = "";

  // iterate through all child elements and collect all text to the buffer
  var child = node.firstChild;
  while (child != null) {
    if (child.nodeType == Node.TEXT_NODE || child.nodeType == Node.CDATA_SECTION_NODE) {
      // append text to buffer
      if (buf != "") {
        buf += " ";
      }
      buf += child.nodeValue;
    }
    child = child.nextSibling;
  }
  return buf;
};
